/*******************************************************************************
 * Copyright (c) 2002, 2003 Boris Pruessmann and others. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Common Public License v0.5 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors: Boris Pruessmann - Initial implementation.
 * (BackgroundUpdateRunnable) David Corbin - Subsequent work.
 ******************************************************************************/
package net.sourceforge.perforce.core.internal;

import com.google.common.collect.Sets;

import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.api.IPerforceServerManager;
import net.sourceforge.perforce.core.internal.events.AddDeleteMoveListener;
import net.sourceforge.perforce.core.internal.utils.Profiler;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.team.core.RepositoryProvider;

import java.util.Collections;
import java.util.Iterator;
import java.util.Set;

/**
 * @version $Revision: 488 $
 * @author <a href="mailto:dcorbin@users.sourceforge.net">David Corbin</a>
 */
public class AdditionMarkerManager implements Runnable {
  // ---------------------------------------------------------------------------
  // Private Variables

  /** Stores the Threadthat is running the monitor. */
  static private Thread thread;

  /** Indicates whether the "Background Update Daemon" is running. */
  private boolean running = true;

  static Profiler p1 = new Profiler("requested");
  static Profiler p2 = new Profiler("expanded");
  static Profiler p3a = new Profiler("processed - existing");
  static Profiler p3b = new Profiler("processed - create");
  static private Set<IResource> requestedResources =
      Collections.synchronizedSet(Sets.<IResource> newHashSet());
  static private Set<IResource> expandedResources =
      Collections.synchronizedSet(Sets.<IResource> newHashSet());
  //----------------------------------------------------------------------------
  // -- Public Methods

  private static Object waitLock = new Object();

  /** @see java.lang.Runnable#run() */
  public void run() {
    thread = Thread.currentThread();

    IPerforceServerManager manager = PerforceCorePlugin.getServerManager();
    while (running) {
      synchronized (waitLock) {
        try {
          waitLock.wait();
        } catch (InterruptedException ignored) {
        }
      }
      if (!running) return;
      try {
        while (running && !requestedResources.isEmpty()) {
          IResource resource;
          synchronized (requestedResources) {
            Iterator<IResource> i = requestedResources.iterator();
            resource = i.next();
            i.remove();
          }
          expandResource(resource);
        }
        while (running && !expandedResources.isEmpty()) {
          IResource resource;
          synchronized (expandedResources) {
            Iterator<IResource> i = expandedResources.iterator();
            resource = i.next();
            i.remove();
          }
          processExpandedResources(resource);
        }
      } catch (CoreException ce) {
        PerforceCorePlugin.logError(ce.getMessage(), ce);
      }
    }
  }

  /** Call this method to start the Background Update Monitor. */
  public void startup() {
    if (thread != null) throw new RuntimeException("Only one such thread per execution");
    new Thread(this, "Perforce Background Addition Marker Manager").start(); //$NON-NLS-1$
  }

  /** Call this method to shutdown the connection monitor. */
  public void shutdown() {
    running = false;
    try {
      synchronized (waitLock) {
        waitLock.notify();
      }
      thread.join();
    } catch (InterruptedException e) {
      // Ignored!
    }
  }

  //----------------------------------------------------------------------------
  // - Private Methods

  public static void createNecessaryMarkers(IResource[] changedResources) {
    if (!PerforceCorePlugin.getPlugin().getShowTasksOnAdd()) return;

    for (int i = 0; i < changedResources.length; i++) {
      p1.exec(changedResources[i]);

      requestedResources.add(changedResources[i]);
    }
    synchronized (waitLock) {
      waitLock.notify();
    }
  }

  private static void expandResource(IResource resource) throws CoreException {
    // Collect resources for which we need to refresh the marker
    final P4TeamProvider provider =
        (P4TeamProvider) RepositoryProvider.getProvider(resource.getProject(), PerforceCorePlugin
            .getTypeId());
    if (provider == null) return;

    p2.exec(resource);
    if (resource.getType() == IResource.FILE) {
      expandedResources.add(resource);
    } else {
      resource.accept(new IResourceVisitor() {
        public boolean visit(IResource resource) {
          if (resource.getType() == IResource.FILE) {
            expandedResources.add(resource);
          }

          return true;
        }
      }, IResource.DEPTH_INFINITE, true);
    }
  }

  static private void processExpandedResources(IResource resource) throws CoreException {
    final P4TeamProvider provider =
        (P4TeamProvider) RepositoryProvider.getProvider(resource.getProject(), PerforceCorePlugin
            .getTypeId());

    if (provider.hasRemote(resource) || provider.isIgnored(resource)) {
      p3a.exec(resource);
      IMarker marker = getAdditionMarker(resource);
      if (marker != null) {
        marker.delete();
      }
    } else {
      p3b.exec(resource);
      createAdditonMarker(resource);
    }
  }

  /**
   * Retrieves the addition marker of the specified resourec.
   * 
   * @param resource the resource.
   * @return the addition marker or null.
   * 
   * @throws CoreException in case of an error.
   */
  private static IMarker getAdditionMarker(IResource resource) throws CoreException {
    IMarker[] markers =
        resource.findMarkers(AddDeleteMoveListener.ADDITION_MARKER, false, IResource.DEPTH_ZERO);
    if (markers.length == 1) {
      return markers[0];
    }

    return null;
  }

  /**
   * Creates a new addition marker for the given resource.
   * 
   * @param resource the resource.
   */
  private static void createAdditonMarker(IResource resource) {
    try {
      IMarker marker = getAdditionMarker(resource);
      if (marker != null) {
        return;
      }

      marker = resource.createMarker(AddDeleteMoveListener.ADDITION_MARKER);
      marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
      marker.setAttribute(IMarker.MESSAGE, "Local addition not under Perforce control");
    } catch (CoreException e) {
      PerforceCorePlugin.log(e.getStatus());
    }
  }
}
